/*
               BUTTLCD -- Butterfly LCD Driver

               Copyright (C) Dean Camera, 2008

            dean [at] fourwalledcubicle [dot] com
                  www.fourwalledcubicle.com
				  
 
*/

/*
   This is a basic driver for the Butterfly LCD. It offers the ability to
   change the contrast and display strings (scrolling or static) from flash
   or SRAM memory only.
   
   This has been completly rewritten from the Atmel code; in this version, as
   much processing as possible is performed by the string display routines
   rather than the interrupt so that the interrupt executes as fast as possible.
*/

#define INC_FROM_DRIVER
#include "LCD_driver.h"

#ifndef BOOL
#define BOOL    char
#define FALSE   0
#define TRUE    (!FALSE)
#endif

// Variable from "button.c" to prevent button-bouncing
extern unsigned char gButtonTimeout;    

//                                  LCD Text            + Nulls for scrolling + Null Termination
static volatile char     TextBuffer[LCD_TEXTBUFFER_SIZE + LCD_DISPLAY_SIZE    + 1] = {};
static volatile uint8_t  StrStart        = 0;
static volatile uint8_t  StrEnd          = 0;
static volatile uint8_t  ScrollCount     = 0;
static volatile uint8_t  UpdateDisplay   = false;
static volatile uint8_t  ShowColons      = false;
       volatile uint8_t  ScrollFlags     = 0;

const           uint16_t LCD_SegTable[] PROGMEM =
{
    0xEAA8,     // '*'
    0x2A80,     // '+'
    0x4000,     // ','
    0x0A00,     // '-'
    0x0A51,     // '.' Degree sign
    0x4008,     // '/'
    0x5559,     // '0'
    0x0118,     // '1'
    0x1e11,     // '2
    0x1b11,     // '3
    0x0b50,     // '4
    0x1b41,     // '5
    0x1f41,     // '6
    0x0111,     // '7
    0x1f51,     // '8
    0x1b51,     // '9'
    0x0000,     // ':' (Not defined)
    0x0000,     // ';' (Not defined)
    0x8008,     // '<'
    0x1A00,     // '='
    0x4020,     // '>'
    0x0000,     // '?' (Not defined)
    0x0000,     // '@' (Not defined)
    0x0f51,     // 'A' (+ 'a')
    0x3991,     // 'B' (+ 'b')
    0x1441,     // 'C' (+ 'c')
    0x3191,     // 'D' (+ 'd')
    0x1e41,     // 'E' (+ 'e')
    0x0e41,     // 'F' (+ 'f')
    0x1d41,     // 'G' (+ 'g')
    0x0f50,     // 'H' (+ 'h')
    0x2080,     // 'I' (+ 'i')
    0x1510,     // 'J' (+ 'j')
    0x8648,     // 'K' (+ 'k')
    0x1440,     // 'L' (+ 'l')
    0x0578,     // 'M' (+ 'm')
    0x8570,     // 'N' (+ 'n')
    0x1551,     // 'O' (+ 'o')
    0x0e51,     // 'P' (+ 'p')
    0x9551,     // 'Q' (+ 'q')
    0x8e51,     // 'R' (+ 'r')
    0x9021,     // 'S' (+ 's')
    0x2081,     // 'T' (+ 't')
    0x1550,     // 'U' (+ 'u')
    0x4448,     // 'V' (+ 'v')
    0xc550,     // 'W' (+ 'w')
    0xc028,     // 'X' (+ 'x')
    0x2028,     // 'Y' (+ 'y')
    0x5009,     // 'Z' (+ 'z')
    0x1441,     // '['
    0x8020,     // '\'
    0x1111,     // ']'
    0x0000,     // '^' (Not defined)
    0x1000      // '_'
};

const uint8_t clocks[] PROGMEM = {8, 4, 2, 1};
const char hexnibble[] PROGMEM = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};


// ======================================================================================

/*
 NAME:      | LCD_Init
 PURPOSE:   | Initializes the Butterfly's LCD for correct operation, ready to display data
 ARGUMENTS: | None
 RETURNS:   | None
*/
void LCD_Init(void)
{
   // Set the initial contrast level to maximum:
   LCD_CONTRAST_LEVEL(0x0F);

    // Select asynchronous clock source, enable all COM pins and enable all segment pins:
    LCDCRB  = (1<<LCDCS) | (3<<LCDMUX0) | (7<<LCDPM0);

    // Set LCD prescaler to give a framerate of 64Hz:
    LCDFRR  = (0<<LCDPS0) | (3<<LCDCD0);   

   // Enable LCD and set low power waveform, enable start of frame interrupt:
    LCDCRA  = (1<<LCDEN) | (1<<LCDAB) | (1<<LCDIE);
}

/*
 NAME:      | LCD_puts
 PURPOSE:   | Displays a string from flash onto the Butterfly's LCD
 ARGUMENTS: | Pointer to the start of the flash string
 RETURNS:   | None
*/
void LCD_puts_f(const char *FlashData)
{
   /* Rather than create a new buffer here (wasting RAM), the TextBuffer global
      is re-used as a temp buffer. Once the ASCII data is loaded in to TextBuffer,
      LCD_puts is called with it to post-process it into the correct format for the
      LCD interrupt.                                                                */

   strcpy_P((char*)&TextBuffer[0], FlashData);
   LCD_puts((char*)&TextBuffer[0]);
}

/*
 NAME:      | LCD_puts
 PURPOSE:   | Displays a string from SRAM onto the Butterfly's LCD
 ARGUMENTS: | Pointer to the start of the SRAM string
 RETURNS:   | None
*/
void LCD_puts(const char *Data)
{
   uint8_t LoadB       = 0;
   uint8_t CurrByte;

   do
   {
      CurrByte = *(Data++);
      
      switch (CurrByte)
      {
         case 'a'...'z':
            CurrByte &= ~(1 << 5);                   // Translate to upper-case character
         case '*'...'_':                                // Valid character, load it into the array
            TextBuffer[LoadB++] = (CurrByte - '*');
            break;
         case 0x00:                                   // Null termination of the string - ignore for now so the nulls can be appended below
            break;
         default:                                     // Space or invalid character, use 0xFF to display a blank
            TextBuffer[LoadB++] = LCD_SPACE_OR_INVALID_CHAR;
      }
   }
   while (CurrByte && (LoadB < LCD_TEXTBUFFER_SIZE));

   ScrollFlags = ((LoadB > LCD_DISPLAY_SIZE)? LCD_FLAG_SCROLL : 0x00);

   for (uint8_t Nulls = 0; Nulls < 7; Nulls++)
     TextBuffer[LoadB++] = LCD_SPACE_OR_INVALID_CHAR;  // Load in nulls to ensure that when scrolling, the display clears before wrapping
   
   TextBuffer[LoadB] = 0x00;                           // Null-terminate string
   
   StrStart      = 0;
   StrEnd        = LoadB;
   ScrollCount   = LCD_SCROLLCOUNT_DEFAULT + LCD_DELAYCOUNT_DEFAULT;
   UpdateDisplay = true;
}


/*
 NAME:      | LCD_vect (ISR, blocking)
 PURPOSE:   | ISR to handle the display and scrolling of the current display string onto the LCD
 ARGUMENTS: | None
 RETURNS:   | None
*/
ISR(LCD_vect)
{
   if (ScrollFlags & LCD_FLAG_SCROLL)
   {
      if (!(ScrollCount--))
      {
         UpdateDisplay = true;
         ScrollCount   = LCD_SCROLLCOUNT_DEFAULT;
      }
   }

   if (UpdateDisplay)
   {
      for (uint8_t Character = 0; Character < LCD_DISPLAY_SIZE; Character++)
      {
         uint8_t Byte = (StrStart + Character);

         if (Byte >= StrEnd)
           Byte -= StrEnd;
         
         LCD_WriteChar(TextBuffer[Byte], Character);
      }
      
      if ((StrStart + LCD_DISPLAY_SIZE) == StrEnd)    // Done scrolling message on LCD once
        ScrollFlags |= LCD_FLAG_SCROLL_DONE;
      
      if (StrStart++ == StrEnd)
        StrStart     = 1;

       if (ShowColons)
            *((uint8_t*)(LCD_LCDREGS_START + 8)) = 0x01;
        else
            *((uint8_t*)(LCD_LCDREGS_START + 8)) = 0x00;

      UpdateDisplay  = false;                         // Clear LCD management flags, LCD update is complete
   }
   
       static char timeout_count;

/**************** Button timeout for button.c ****************/
    if(!gButtonTimeout)
    {
        timeout_count++;
        
        if(timeout_count > 9)
        {
            gButtonTimeout = TRUE;
            timeout_count = 0;
        }
    }
}

/*
 NAME:      | LCD_WriteChar (static, inline)
 PURPOSE:   | Routine to write a character to the correct LCD registers for display
 ARGUMENTS: | Character to display, LCD character number to display character on
 RETURNS:   | None
*/

static inline void LCD_WriteChar(const uint8_t Byte, const uint8_t Digit)
{
	uint8_t* BuffPtr = (uint8_t*)(LCD_LCDREGS_START + (Digit >> 1));
	uint16_t SegData = 0x0000;
	
	if (Byte != LCD_SPACE_OR_INVALID_CHAR)              // Null indicates invalid character or space
		SegData = pgm_read_word(&LCD_SegTable[Byte]);   

	for (uint8_t BNib = 0; BNib < 4; BNib++)
	{
		uint8_t MaskedSegData = (SegData & 0x0000F);

		if (Digit & 0x01)
			*BuffPtr = ((*BuffPtr & 0x0F) | (MaskedSegData << 4));
		else
			*BuffPtr = ((*BuffPtr & 0xF0) | MaskedSegData);

		BuffPtr += 5;
		SegData >>= 4;
	}   
}


static inline void LCD_putc(const uint8_t Digit, const uint8_t Byte)
{
	uint8_t* BuffPtr = (uint8_t*)(LCD_LCDREGS_START + (Digit >> 1));
	uint16_t SegData = 0x0000;
	
	UpdateDisplay = false;
	ScrollFlags = LCD_FLAG_SCROLL_DONE;
	
	switch (Byte)
	{
		case 'a'...'z':		// Translate to upper-case character
            SegData = pgm_read_word(&LCD_SegTable[Byte - 74]);
			break;
         case '*'...'_':	// Valid character, load it into the array
            SegData = pgm_read_word(&LCD_SegTable[Byte - 42]);
            break;
	}
	
	for (uint8_t BNib = 0; BNib < 4; BNib++)
	{
		uint8_t MaskedSegData = (SegData & 0x0000F);

		if (Digit & 0x01)
			*BuffPtr = ((*BuffPtr & 0x0F) | (MaskedSegData << 4));
		else
			*BuffPtr = ((*BuffPtr & 0xF0) | MaskedSegData);

		BuffPtr += 5;
		SegData >>= 4;
	}   
}


void LCD_ShowColons(const uint8_t ColonsOn)
{
   ShowColons    = ColonsOn;
   UpdateDisplay = true;
}


void hexstr(char *somestring, uint8_t  somebyte) {
	somestring[0] = (const char)(pgm_read_byte(&hexnibble[somebyte >> 4]));
	somestring[1] = (const char)(pgm_read_byte(&hexnibble[somebyte & 0x0F]));
    somestring[2] = 0;
}


void show1hex (uint8_t byte1) {
	LCD_putc(0, (const char)(pgm_read_byte(&hexnibble[byte1 >> 4])));
	LCD_putc(1, (const char)(pgm_read_byte(&hexnibble[byte1 & 0x0F])));
	LCD_putc(2, ' ');
	LCD_putc(3, 'H');
	LCD_putc(4, 'E');
	LCD_putc(5, 'X');
}


void show2hex (uint8_t byte1, uint8_t byte2) {
	LCD_putc(0, (const char)(pgm_read_byte(&hexnibble[byte1 >> 4])));
	LCD_putc(1, (const char)(pgm_read_byte(&hexnibble[byte1 & 0x0F])));
	LCD_putc(2, ' ');
	LCD_putc(3, ' ');
	LCD_putc(4, (const char)(pgm_read_byte(&hexnibble[byte2 >> 4])));
	LCD_putc(5, (const char)(pgm_read_byte(&hexnibble[byte2 & 0x0F])));
}

void show3hex (uint8_t byte1, uint8_t byte2, uint8_t byte3 ) {
	LCD_putc(0, (const char)(pgm_read_byte(&hexnibble[byte1 >> 4])));
	LCD_putc(1, (const char)(pgm_read_byte(&hexnibble[byte1 & 0x0F])));
	LCD_putc(2, (const char)(pgm_read_byte(&hexnibble[byte2 >> 4])));
	LCD_putc(3, (const char)(pgm_read_byte(&hexnibble[byte2 & 0x0F])));
	LCD_putc(4, (const char)(pgm_read_byte(&hexnibble[byte3 >> 4])));
	LCD_putc(5, (const char)(pgm_read_byte(&hexnibble[byte3 & 0x0F])));
}

void show12bits (uint16_t arg1, uint16_t arg2) {
	LCD_putc(0, (const char)(pgm_read_byte(&hexnibble[arg1 >> 8 & 0x000F])));
	LCD_putc(1, (const char)(pgm_read_byte(&hexnibble[arg1 >> 4 & 0x000F])));
	LCD_putc(2, (const char)(pgm_read_byte(&hexnibble[arg1 & 0x000F])));
	LCD_putc(3, (const char)(pgm_read_byte(&hexnibble[arg2 >> 8 & 0x000F])));
	LCD_putc(4, (const char)(pgm_read_byte(&hexnibble[arg2 >> 4 & 0x000F])));
	LCD_putc(5, (const char)(pgm_read_byte(&hexnibble[arg2 & 0x000F])));

}

void show_mhz (void) {
	uint8_t somebyte = (const char)(pgm_read_byte(&clocks[CLKPR]));
	LCD_putc(0, (const char)(pgm_read_byte(&hexnibble[somebyte >> 4])));
	LCD_putc(1, (const char)(pgm_read_byte(&hexnibble[somebyte & 0x0F])));	
	LCD_putc(2, ' ');
	LCD_putc(3, 'M');
	LCD_putc(4, 'h');
	LCD_putc(5, 'z');
}
